﻿using System;

namespace Bouyei.Geo.Geometries
{
    public class GeoHashBase32
    {
        // Base32 编码字符表
        private static readonly char[] Base32 = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'b', 'c', 'd', 'e', 'f', 'g',
        'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
        's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        };
        private static readonly int[,] NeighborOffset = {
        { -1, -1 }, { -1, 0 }, { -1, +1 },
        {  0, -1 },           {  0, +1 },
        { +1, -1 }, { +1, 0 }, { +1, +1 }
        };

        // 反向查找每个字符在数组中的索引
        private static readonly sbyte[] Decodex = new sbyte[128];
        private static readonly int[] BitTable = { 16, 8, 4, 2, 1 };

        static GeoHashBase32()
        {
            for (int i = 0; i < Decodex.Length; i++)
                Decodex[i] = -1;
            for (int i = 0; i < Base32.Length; i++)
                Decodex[(int)Base32[i]] = (sbyte)i;
        }

        /// <summary>
        /// 将经纬度编码为 Geohash 字符串
        /// </summary>
        //public static string Encode(double latitude, double longitude, int precision = 12)
        //{
        //    bool isEven = true;
        //    double latMin = -90, latMax = 90;
        //    double lonMin = -180, lonMax = 180;
        //    int bit = 0, ch = 0;
        //    string geohash = "";

        //    while (geohash.Length < precision)
        //    {
        //        if (isEven)
        //        {
        //            double mid = (lonMin + lonMax) / 2;
        //            if (longitude > mid)
        //            {
        //                ch |= BitTable[bit];
        //                lonMin = mid;
        //            }
        //            else
        //            {
        //                lonMax = mid;
        //            }
        //        }
        //        else
        //        {
        //            double mid = (latMin + latMax) / 2;
        //            if (latitude > mid)
        //            {
        //                ch |= BitTable[bit];
        //                latMin = mid;
        //            }
        //            else
        //            {
        //                latMax = mid;
        //            }
        //        }

        //        isEven = !isEven;
        //        if (bit < 4)
        //        {
        //            bit++;
        //        }
        //        else
        //        {
        //            geohash += Base32[ch];
        //            bit = 0;
        //            ch = 0;
        //        }
        //    }

        //    return geohash;
        //}

        //
        
        public static string Encode(double latitude, double longitude, int precision = 12)
        {
            bool isEven = true;
            double latMin = -90, latMax = 90;
            double lonMin = -180, lonMax = 180;
            ulong bits = 0;

            char[] result = new char[precision];

            for (int chIdx = 0; chIdx < precision; chIdx++)
            {
                for (int i = 0; i < 5 && chIdx < precision; i++)
                {
                    if (isEven)
                    {
                        double mid = (lonMin + lonMax) / 2;
                        if (longitude > mid)
                        {
                            bits |= (ulong)(1 << (4 - i));
                            lonMin = mid;
                        }
                        else
                        {
                            lonMax = mid;
                        }
                    }
                    else
                    {
                        double mid = (latMin + latMax) / 2;
                        if (latitude > mid)
                        {
                            bits |= (ulong)(1 << (4 - i));
                            latMin = mid;
                        }
                        else
                        {
                            latMax = mid;
                        }
                    }
                    isEven = !isEven;
                }

                result[chIdx] = Base32[bits & 0x1F]; // 取低5位
                bits >>= 5;
            }

            return new string(result);
        }
 
        /// <summary>
        /// 将 Geohash 解码为经纬度范围
        /// </summary>
        public static (double latMin, double latMax, double lonMin, double lonMax) DecodeRange(string geohash)
        {
            bool isEven = true;
            double latMin = -90, latMax = 90;
            double lonMin = -180, lonMax = 180;

            foreach (char c in geohash)
            {
                int ch = Decodex[c];
                if (ch == -1)
                    throw new ArgumentException("Invalid geohash character.");

                for (int i = 0; i < 5; i++)
                {
                    int bits = (ch >> (4 - i)) & 1;
                    if (isEven)
                    {
                        double mid = (lonMin + lonMax) / 2;
                        if (bits == 1)
                            lonMin = mid;
                        else
                            lonMax = mid;
                    }
                    else
                    {
                        double mid = (latMin + latMax) / 2;
                        if (bits == 1)
                            latMin = mid;
                        else
                            latMax = mid;
                    }
                    isEven = !isEven;
                }
            }
            return (latMin, latMax, lonMin, lonMax);
        }

        /// <summary>
        /// 解析坐标
        /// </summary>
        /// <param name="geohash"></param>
        /// <returns></returns>
        public static (double latitude, double longitude) Decode(string geohash)
        {
            var range = DecodeRange(geohash);
            return ((range.latMin + range.latMax) / 2, (range.lonMin + range.lonMax) / 2);
        }

        /// <summary>
        /// 获取邻居的8个坐标点
        /// </summary>
        /// <param name="geohash"></param>
        /// <returns></returns>
        public static string[] GetNeighbors(string geohash)
        {
            var result = new string[8];
            for (int i = 0; i < 8; i++)
            {
                result[i] = MoveGeohash(geohash, NeighborOffset[i, 0], NeighborOffset[i, 1]);
            }
            return result;
        }

        private static string MoveGeohash(string geohash, int dx, int dy)
        {
            // 解码 base hash 到边界范围
            var (latMin, latMax, lonMin, lonMax) = DecodeRange(geohash);
            double latMid = (latMin + latMax) / 2;
            double lonMid = (lonMin + lonMax) / 2;

            // 计算新的经纬度中心点
            double moveLat = latMax - latMin;
            double moveLon = lonMax - lonMin;

            double newLat = latMid + dy * moveLat;
            double newLon = lonMid + dx * moveLon;
            return Encode(newLat, newLon, geohash.Length);
        }
    }
}
